; CTM2EXE - converts .COM file into .EXE file with 128 bytes stack
; Copyright (c) 2000 by Joergen Ibsen / Jibz <jibz@hotmail.com>
; Adopted, bugfixed and expanded by Arkady V.Belousov <ark@mos.ru>
;
; This program is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program; if not, write to the Free Software
; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
;

; TODO:
; - optionally define [stacksize] in the command line (possible range
;   32..65534 or $20..$FFFE); default [stacksize]=-1
; - optionally output file name must be passed in the command line
;   (can be equal to input file name)

j		equ	jmp short

movSeg		macro	dest,src
		push	src
		pop	dest
	endm

PrintS		macro	addr
	IFNB <addr>
	 IFDIFI <addr>,<dx>
		mov	dx,offset DGROUP:addr
	 ENDIF
	ENDIF
		mov	ah,9
		int	21h
	endm

DGROUP		group	_TEXT,_DATA
		assume	cs:DGROUP,ss:_STACK

_TEXT		segment word public
_TEXT		ends
_DATA		segment word public
_DATA		ends
EXE_DATA	segment word public
EXE_DATA	ends
_STACK		segment para stack
_STACK		ends

;圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹

_DATA		segment word public

EXEseg		dw	SEG EXE_DATA
stacksize	dw	128

E_		db	"Error: $"
E_format	db	"source file already .EXE or too big$"
E_open		db	"can't open file$"
E_create	db	"can't create file$"
E_read		db	"can't read from file$"
E_write		db	"can't write to file$"
E_diskfull	db	"can't write - disk full$"
S_stackwarn	db	"Warning: specified stack size too big",0dh,0ah,'$'

Copyright	db	'CTM2EXE v1.0 Copyright (c) 2000 by Joergen Ibsen / Jibz, Arkady V.Belousov',0dh,0ah
CRLF		db	0dh,0ah,'$'
Syntax		db	'Syntax: ctm2exe <filename>',0dh,0ah
		db	0dh,0ah
		db	'CTM2EXE will convert the .COM into a .EXE file by adding appropriate header.',0dh,0ah
		db	'The 128 bytes stack is placed right after the image. The original file will',0dh,0ah
		db	'be overwritten.$'

_DATA		ends

;圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹

_TEXT		segment word public

start:		push	ds
		movSeg	ds,cs
		assume	cs:DGROUP,ds:DGROUP
		PrintS	Copyright

;---------- parse command line to get file name
		pop	ds
		assume	ds:nothing		; DS=PSP

		mov	si,81h
@@skipspaces:	lodsb
		cmp	al,' '
		je	@@skipspaces
		mov	dx,offset DGROUP:Syntax
		jb	EXITERRMSG		; no filename given

		mov	dx,si
		dec	dx			; ds:dx -> filename
@@nameloop:	lodsb
		cmp	al,' '
		ja	@@nameloop

;---------- open, read and check file format
		push	dx ds
		mov	byte ptr [si-1],0	; zero terminate filename
		call	openfile
		call	getfilestamp		; save date/time stamp
		pop	di si
		push	cx dx si di

		mov	ds,[EXEseg]
		assume	ds:EXE_DATA
		mov	dx,offset EXE_DATA:buffer
		mov	di,dx
		;mov	[di],'MZ'		; protect from empty file
		mov	cx,BUFFERSZ
		call	readfile

		mov	dx,offset DGROUP:E_format
		cmp	ax,cx
		je	EXITERR			; too big?
		cmp	[di],'MZ'
		je	EXITERR			; already .EXE?
		cmp	[di],'ZM'
		je	EXITERR			; already .EXE?

		mov	si,ax			; file size
		call	closefile

;---------- create file, prepare header and write file
		pop	ds dx
		assume	ds:nothing		; DS=PSP
		call	createfile

		mov	ax,[stacksize]
		mov	ds,[EXEseg]
		assume	ds:EXE_DATA
		call	prepareEXE

		mov	dx,offset EXE_DATA:EXEheader
		call 	writefile

		pop	dx cx
		call	setfilestamp		; restore date/time stamp
		call	closefile

;---------- exit with success
		mov	ax,4C00h
		int	21h			; terminate, al=return code

;哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌
;			Exit with message
;苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘
;
; In:	DX					(message)
; Use:	none
; Call:	INT 21/4C
;
EXITERR		proc
		push	dx
		movSeg	ds,cs
		assume	ds:DGROUP
		PrintS	E_
		pop	dx
EXITERRMSG:	mov	bx,4C01h
		movSeg	ds,cs
		assume	ds:DGROUP
		PrintS
		PrintS	CRLF
		xchg	ax,bx			; OPTIMIZE: instead MOV AX,BX
		int	21h			; terminate, al=return code
EXITERR		endp

;哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌
;				Open file
;苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘
;
; In:	DS:DX					(file name pointer)
; Out:	BX					(handle)
; Use:	E_create
; Modf:	AX, CX, DX
; Call:	INT 21/3D, EXITERR
;
openfile	proc
		xor	cx,cx
		mov	ax,3D40h		; READ ONLY/DENYNONE
		int	21h
		mov	dx,offset DGROUP:E_open
		j	@openret
openfile	endp

;哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌
;				Create file
;苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘
;
; In:	DS:DX					(file name pointer)
; Out:	BX					(handle)
; Use:	E_create
; Modf:	AX, CX, DX
; Call:	INT 21/3D, INT 21/3C, EXITERR
;
createfile	proc
		xor	cx,cx
		mov	ah,3Ch
		int	21h
		mov	dx,offset DGROUP:E_create
@openret:	jc	EXITERR
		xchg	bx,ax			; OPTIMIZE: instead MOV BX,AX
		ret
createfile	endp

;哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌
;				Read from file
;苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘
;
; In:	BX					(handle)
;	CX					(buffer size)
;	DS:DX					(buffer)
; Out:	AX					(# of bytes read)
; Use:	E_read
; Modf:	DX
; Call:	INT 21/3F, EXITERR
;
readfile	proc
		mov	ah,3Fh
		int	21h
		mov	dx,offset DGROUP:E_read
		jc	EXITERR
		ret
readfile	endp

;哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌
;				Write to file
;苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘
;
; In:	BX					(handle)
;	CX					(buffer size)
;	DS:DX					(buffer)
; Out:	none
; Use:	E_write, E_diskfull
; Modf:	AX, DX
; Call:	INT 21/40, EXITERR
;
writefile	proc
		mov	ah,40h
		int	21h
		mov	dx,offset DGROUP:E_write
		jc	EXITERR
		mov	dx,offset DGROUP:E_diskfull
		cmp	ax,cx
		jne	EXITERR
		ret
writefile	endp

;哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌
;			Get file date/time stamp
;苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘
;
; In:	BX					(handle)
; Out:	Carry flag				("func # invalid" or
;						 "invalid handle")
;	CX					(file's time)
;	DX					(file's date)
; Use:	none
; Modf:	AX
; Call:	INT 21/5700
;
getfilestamp	proc
		mov	ax,5700h
		int	21h
		ret
getfilestamp	endp

;哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌
;			Set file date/time stamp
;苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘
;
; In:	BX					(handle)
;	CX					(file's time)
;	DX					(file's date)
; Out:	Carry flag				("func # invalid" or
;						 "invalid handle")
; Use:	none
; Modf:	AX
; Call:	INT 21/5701
;
setfilestamp	proc
		mov	ax,5701h
		int	21h
		ret
setfilestamp	endp

;哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌
;				Close file
;苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘
;
; In:	BX					(handle)
; Out:	Carry flag				("invalid handle")
; Use:	none
; Modf:	AX
; Call:	none
;
closefile	proc
		mov	ah,3Eh
		int	21h
		ret
closefile	endp

;哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌哌
;			Prepare EXE file header
;苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘苘
;
; In:	AX					(requested stack size)
;	SI					(file size)
;	DS = EXE_DATA
; Out:	CX					(converted file size)
; Use:	EXEheadersz
; Modf:	AX, DX, SI, min_mem, h_sp, num_pages, last_page
; Call:	none
;
prepareEXE	proc
		assume	ds:EXE_DATA
		mov	dx,ax			; original stack size

;---------- compute program end for save in SP
		mov	cx,0FFFEh		; 64K-2
		add	ax,si			; code+stack
		jc	@@savestack		; jump if >= 64K
		inc	ah			; prog=PSP+code+stack
		jz	@@savestack		; jump if >= 64K
		dec	ax
		or	al,0Eh			; roundup(prog / 16) * 16
		and	al,not 1		; - 2
		xchg	cx,ax			; OPTIMIZE: instead MOV CX,AX

;---------- save calculated SP and min_mem
@@savestack:	xchg	ax,cx			; OPTIMIZE: instead MOV AX,CX
		mov	[h_sp],ax
		sub	ax,si			; -code
		sub	ax,0100h-2		; -PSP+2
		mov	cl,4
		shr	ax,cl
		mov	[min_mem],ax

;---------- if stack specified, init [max_mem] and give warning if
;	    specified stack > [min_mem]*16+(-filesize)%16
		inc	dx			; -1 mean "not forced"
		jz	@@calcsize
		mov	[max_mem],ax
		shl	ax,cl			; [min_mem]*16
		mov	cx,si
		neg	cx
		and	cx,0Fh
		add	ax,cx			; +(-code)%16
		dec	dx
		cmp	dx,ax
		jbe	@@calcsize
		push	ds
		movSeg	ds,cs
		assume	ds:DGROUP
		PrintS	S_stackwarn
		pop	ds
		assume	ds:EXE_DATA

;---------- calculate program size fields
@@calcsize:	add	si,EXEheadersz		; si = converted size
		mov	cl,9
		mov	ax,si
		shr	si,cl			; size / 512
		mov	cx,ax
		and	ax,1FFh			; size % 512
		jz	@@savesize		; protection from overflow
		inc	si			; roundup(size / 512)
@@savesize:	mov	[num_pages],si
		mov	[last_page],ax
		ret
prepareEXE	endp

_TEXT		ends

;圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹

EXE_DATA	segment word public

EXEheader	label
		db	'MZ'		; signature
last_page	dw	?
num_pages	dw	?
		dw	0		; # of relocations
		dw	2		; header size (para)
min_mem		dw	?
max_mem		dw	0FFFFh
		dw	0FFF0h		; SS offset; -10h for PSP
h_sp		dw	?
crc		dw	0
		dw	00100h		; start IP
		dw	0FFF0h		; CS offset; -10 for PSP
		dw	0001Ch		; relocations offset
		dw	0		; # of overlay
		db	4 DUP(0)	; reserved
EXEheadersz	equ	($-EXEheader)
ERRIF (EXEheadersz ne 32) "EXE header definition corrupted!"

BUFFERSZ	equ	0FF00h-32	; 64K-PSP-(32 bytes of min stack)
buffer		db	'MZ',BUFFERSZ-2 DUP(?)

EXE_DATA	ends

_STACK		segment para stack
		db	128 DUP(?)
_STACK		ends

		end	start
